<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

// namespace spica\core\session\store;

/**
 * This class provides MongoDB storage backend for session data.
 *
 * @category   spica
 * @package    core
 * @subpackage session\store
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 06, 2010
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: MongoDB.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaSessionMongoDBStore /* implements SpicaSessionStore */
{
    /**
     * Session lifetime.
     *
     * @var int
     */
    protected $_lifeTime;

    /**
     * Socket to connect to MongoDB server.
     *
     * @var Mongo
     */
    protected $_driver = null;

    /**
     * MongoDB database connection.
     *
     * @var MongoDB
     */
    protected $_conn;

    /**
     * MongoDB collection.
     * 
     * @var MongoCollection
     */
    protected $_collection;

    /**
     * Storage config.
     *
     * @var array
     */
    private $_config;

    /**
     * Constructs an object of <code>SpicaSessionMongoDBStore</code>.
     *
     * @param array $config [host, port, db, dbtable] Defaults to an empty array
     */
    public function __construct($config = array())
    {
        $default = array(
            'host' => '127.0.0.1',
            'port' => 27017, // MongoDB daemon port - web interface port 28017
            'db'   => 'session',
            'dbtable' => 'user',  // MongoDB collection name
            'dbcol_sid' => 'sid', // column named sid to store session ID
            'dbcol_sdata' => 'sdata', // column named sdata to store session data
            'dbcol_stime' => 'stime', // column named stime to store session update time
            'persistent' => false,
            'weight' => 1,
            'timeout' => 1, // connection timeout
            'retry_interval' => 3
        );

        $this->_config = array_merge($default, $config);
    }

    /**
     * Opens session
     *
     * @param string $savePath ignored
     * @param string $sessName ignored
     * @return bool
     */
    public function open($savePath, $sessName)
    {
        try
        {
            $this->_lifeTime = ini_get('session.gc_maxlifetime');
            $this->_driver = new Mongo(sprintf("%s:%s", $this->_config['host'], $this->_config['port'])); // MongoConnectionException
            $this->_conn = $this->_driver->selectDB($this->_config['db']); // InvalidArgumentException
            $this->_collection = $this->_conn->selectCollection($this->_config['dbtable']);
        }
        catch (Exception $e)
        {
            trigger_error("Error occured on establishing a connection to MongoDB's '{$this->_config['db']}' database: ".$e->getMessage(), E_USER_NOTICE);
            return false;
        }
        
        return true;
    }

    /**
     * Fetches session data
     *
     * @param  string $sid
     * @return string
     */
    public function read($sid)
    {
        if (null === $this->_collection)
        {
            return false;
        }

        try
        {
            // Result set (array)
            $rs = $this->_collection->findOne(array($this->_config['dbcol_sid'] => $sid)); // MongoConnectionException

            if (!empty($rs))
            {
                // Found
                return $rs[$this->_config['dbcol_sdata']];
            }
        }
        catch (Exception $e)
        {
            trigger_error("Error occured on retrieving session data '$sid' from MongoDB's '{$this->_config['db']}' database: ".$e->getMessage(), E_USER_NOTICE);
        }

        return '';
    }

    /**
     * Closes session
     *
     * @return bool
     */
    public function close()
    {
        if (null === $this->_driver)
        {
            return false;
        }

        $this->_driver->close();
    }

    /**
     * Updates session.
     *
     * @param  string $sid Session ID
     * @param  string $data
     * @return bool
     */
    public function write($sid, $data)
    {
        if (null === $this->_collection)
        {
            return false;
        }

        try
        {
            $rs = array(
                $this->_config['dbcol_sid'] => $sid,
                $this->_config['dbcol_sdata'] => $data,
                $this->_config['dbcol_stime'] => time(),
            );

            if (true === $this->_collection->update(array($this->_config['dbcol_sid'] => $sid), $rs, array('upsert' => true)))
            {
                return true;
            }

            // can't create record
            $error = $this->_conn->lastError();
            trigger_error("Error occured on writing session data into MongoDB's '{$this->_config['db']}' database: ".$error['err'], E_USER_NOTICE);
        }
        catch (Exception $e)
        {
            trigger_error("Error occured on writing session data '$sid' into MongoDB's '{$this->_config['db']}' database: ".$e->getMessage(), E_USER_NOTICE);
        }

        return false;
    }

    /**
     * Destroys session provided with ID.
     *
     * @param  string $sid
     * @return bool
     */
    public function destroy($sid)
    {
        if (null === $this->_collection)
        {
            return false;
        }
        
        if (true === $this->_collection->remove(array($this->_config['dbcol_sid'] => $sid)))
        {
            return true;
        }
        
        $error = $this->_conn->lastError();
        trigger_error(sprintf('Unable to destroy session id "%s" (%s).', $sid, $error['err']), E_USER_NOTICE);
        return false;
    }

    /**
     * Garbage collection
     *
     * @param  int $sessMaxLifeTime ignored
     * @return bool
     */
    public function gc($sessMaxLifeTime)
    {
        if (null === $this->_collection)
        {
            return false;
        }
        
        // delete the record older than the authorised session life time
        // using value at the column configured at 'col_time'
        if (true === $this->_collection->remove(array('$where' => sprintf('this.%s + %d < %d', $this->_config['dbcol_stime'], $sessMaxLifeTime, time()))))
        {
            return true;
        }

        $error = $this->_conn->lastError();
        trigger_error(sprintf('Unable to delete old sessions (%s).', $error['err']), E_USER_NOTICE);
    }

    /**
     * Gets total session.
     *
     * @return int
     */
    public function getSessionCount()
    {
        if (null === $this->_collection)
        {
            return false;
        }

        return $this->_collection->count();
    }
}

?>